package com.xinsoft.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xinsoft.common.exception.BDException;
import com.xinsoft.constant.Const;
import com.xinsoft.entity.dto.InOrOutStock;
import com.xinsoft.entity.dto.ScmProduceConsumingDTO;
import com.xinsoft.entity.dto.ScmProduceConsumingParam;
import com.xinsoft.entity.po.*;
import com.xinsoft.entity.vo.ScmProduceConsumingPageVo;
import com.xinsoft.entity.vo.ScmProduceConsumingVo;
import com.xinsoft.mapper.ScmProduceConsumingItemMapper;
import com.xinsoft.service.*;
import com.xinsoft.mapper.ScmProduceConsumingMapper;
import com.xinsoft.utils.TokenUtil;
import lombok.Synchronized;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ZYM
 * @description 针对表【scm_produce_consuming(生产领用)】的数据库操作Service实现
 * @createDate 2023-08-14 11:30:37
 */
@Service
public class ScmProduceConsumingServiceImpl extends ServiceImpl<ScmProduceConsumingMapper, ScmProduceConsuming>
        implements ScmProduceConsumingService {
    @Resource
    private ScmProduceConsumingMapper produceConsumingMapper;
    @Resource
    private ScmProduceConsumingItemMapper produceConsumingItemMapper;

    @Autowired
    private ScmProduceConsumingItemService produceConsumingItemService;
    @Autowired
    private ScmStockService stockService;
    @Autowired
    private ScmStockHisService stockHisService;
    @Autowired
    private SysDocumentNumService sysDocumentNumService;
    @Autowired
    private MesFeedingPlanDetailService feedingPlanDetailService;

    @Override
    public IPage<ScmProduceConsumingPageVo> findPageListByParam(ScmProduceConsumingParam param) {
        Page<ScmProduceConsumingPageVo> page = new Page<>(param.getCurrentPage(), param.getPageSize());
        IPage<ScmProduceConsumingPageVo> pageList = baseMapper.findListByParam(page, param);
        return pageList;
    }

    @Override
    public List<ScmProduceConsumingPageVo> findListByParam(ScmProduceConsumingParam param) {
        List<ScmProduceConsumingPageVo> list = baseMapper.findListByParam(param);
        return list;
    }

    @Override
    @Synchronized
    public Integer tempSave(ScmProduceConsumingDTO dto) {
        ScmProduceConsuming produceConsuming = new ScmProduceConsuming();
        BeanUtils.copyProperties(dto, produceConsuming);
        produceConsuming.setAuditStatus(Const.AUDIT_STATUS_WAIT);
        List<ScmProduceConsumingItem> itemList = dto.getScmProduceConsumingItemList();
        this.checkStock(itemList);
        // 保存或新增
        if (Objects.isNull(produceConsuming.getId())) {
            // 更新流水号
            sysDocumentNumService.incrementLastSerialByDocumentId(Const.DOCUMENT_ID.PRODUCE_CONSUMING_DOCUMENT);
            this.save(produceConsuming);
        } else {
            produceConsumingItemService.remove(Wrappers.lambdaQuery(ScmProduceConsumingItem.class).eq(ScmProduceConsumingItem::getConsumingId, produceConsuming.getId()));
            this.updateById(produceConsuming);
        }

        // 保存生产入库明细
        itemList.forEach(item -> item.setConsumingId(produceConsuming.getId()));
        produceConsumingItemService.saveBatch(itemList);

        return produceConsuming.getId();
    }

    /**
     * 库存校验
     *
     * @param itemList 生产领用明细
     */
    public void checkStock(List<ScmProduceConsumingItem> itemList) {
        long count = itemList.stream().filter(item -> Objects.isNull(item.getWarehouseId()) || Objects.isNull(item.getWarehItemId()) || Objects.isNull(item.getBatch())).count();
        if (count > 0) {
            throw new BDException("请查询库存");
        }
        itemList.forEach(item -> {
            ScmStock stock = stockService.getOne(Wrappers.lambdaQuery(ScmStock.class)
                    .eq(ScmStock::getMaterialId, item.getMaterialId())
                    .eq(ScmStock::getWarehouseId, item.getWarehouseId())
                    .eq(ScmStock::getWarehItemId, item.getWarehItemId())
                    .eq(ScmStock::getBatchNo, item.getBatch()));
            if (stock.getBatchNumFree().compareTo(item.getConsumingNum()) < 0) {
                throw new BDException("领用数量不能大于库存可用数量");
            }
        });
    }

    @Override
    public ScmProduceConsumingVo detail(Integer id) {
        ScmProduceConsumingVo vo = baseMapper.getById(id);
        vo.setScmProduceConsumingItemList(produceConsumingItemMapper.listByProduceConsumingId(id));
        return vo;
    }

    @Override
    public void del(List<Integer> ids) {
        List<ScmProduceConsuming> list = this.list(Wrappers.lambdaQuery(ScmProduceConsuming.class)
                .eq(ScmProduceConsuming::getAuditStatus, Const.AUDIT_STATUS_NORMAL)
                .in(ScmProduceConsuming::getId, ids));
        if (list.size() > 0) {
            throw new BDException("操作失败，只能删除未审核的生产领用单");
        }
        produceConsumingItemService.remove(Wrappers.lambdaQuery(ScmProduceConsumingItem.class).in(ScmProduceConsumingItem::getConsumingId, ids));
        this.removeByIds(ids);
    }

    @Override
    public void audit(Integer id) {
        ScmProduceConsuming produceConsuming = this.getById(id);
        if (Const.AUDIT_STATUS_NORMAL.equals(produceConsuming.getAuditStatus())) {
            throw new BDException("该单据已审核，无需重复审核");
        }
        // 1.审核
        produceConsuming.setAuditId(TokenUtil.getUserId());
        produceConsuming.setAuditTime(new Date());
        produceConsuming.setAuditStatus(Const.AUDIT_STATUS_NORMAL);
        this.updateById(produceConsuming);
        // 2.反写投料计划已领数量
        this.updateFeedingPlanDetail(id, Const.AUDIT_STATUS_NORMAL);
        // 3.更新库存
        List<InOrOutStock> inOrOutStockList = this.getInOrOutStockList(id);
        stockService.updateInventory(Const.SourceBillType.SCLY, produceConsuming.getConsumingCode(), inOrOutStockList, Const.ChangeType.SUBTRACT);
    }

    /**
     * 更新投料计划领用数量
     *
     * @param id            生产领用id
     * @param operationType 操作类型（审核、反审核）
     */
    public void updateFeedingPlanDetail(Integer id, Integer operationType) {
        List<ScmProduceConsumingItem> items = produceConsumingItemService.list(Wrappers.lambdaQuery(ScmProduceConsumingItem.class)
                .eq(ScmProduceConsumingItem::getConsumingId, id));
        List<Integer> sourceIds = items.stream().map(ScmProduceConsumingItem::getSourceId).distinct().collect(Collectors.toList());
        List<Integer> materialIds = items.stream().map(ScmProduceConsumingItem::getMaterialId).distinct().collect(Collectors.toList());
        Map<String, BigDecimal> resultMap = items.stream()
                .collect(Collectors.groupingBy(item -> String.join("_", item.getSourceId().toString(), item.getMaterialId().toString()),
                        Collectors.reducing(BigDecimal.ZERO, ScmProduceConsumingItem::getConsumingNum, BigDecimal::add)));
        List<MesFeedingPlanDetail> feedingPlanDetails = feedingPlanDetailService.list(Wrappers.lambdaQuery(MesFeedingPlanDetail.class)
                .in(MesFeedingPlanDetail::getFeedingPlanId, sourceIds)
                .in(MesFeedingPlanDetail::getMaterialId, materialIds));
        feedingPlanDetails.forEach(detail -> {
            String key = String.join("_", detail.getFeedingPlanId().toString(), detail.getMaterialId().toString());
            if (resultMap.containsKey(key)) {
                // 审核，增加领用数量
                if (operationType.equals(Const.AUDIT_STATUS_NORMAL)) {
                    detail.setGetNum(Objects.isNull(detail.getGetNum()) ? resultMap.get(key) : resultMap.get(key).add(detail.getGetNum()));
                    detail.setGetAssistNum(detail.getGetNum().multiply(detail.getConvCoefficient()));
                }
                // 反审核，减少领用数量
                if (operationType.equals(Const.AUDIT_STATUS_WAIT)) {
                    detail.setGetNum(resultMap.get(key).subtract(detail.getGetNum()));
                    detail.setGetAssistNum(detail.getGetNum().multiply(detail.getConvCoefficient()));
                }
            }
        });
        // 更新领用数量
        feedingPlanDetailService.updateBatchById(feedingPlanDetails);
    }

    /**
     * 获取出入库明细列表
     * @param id
     * @return {@link List}<{@link InOrOutStock}>
     */
    public List<InOrOutStock> getInOrOutStockList(Integer id) {
        List<InOrOutStock> list = new ArrayList<>();
        List<ScmProduceConsumingItem> items = produceConsumingItemService.list(Wrappers.lambdaQuery(ScmProduceConsumingItem.class).eq(ScmProduceConsumingItem::getConsumingId, id));
        items.forEach(item -> {
            InOrOutStock inOrOutStock = new InOrOutStock();
            BeanUtils.copyProperties(item,inOrOutStock);
            inOrOutStock.setQty(item.getConsumingNum());
            list.add(inOrOutStock);
        });
        return list;
    }

    @Override
    public void reverseAudit(Integer id) {
        ScmProduceConsuming produceConsuming = this.getById(id);
        if (Const.AUDIT_STATUS_WAIT.equals(produceConsuming.getAuditStatus())) {
            throw new BDException("该单据未审核，无需反审核");
        }
        int citeCount = produceConsumingMapper.getProduceConsumingCiteCount(id);
        if (citeCount > 0) {
            throw new BDException("该单据已产生下游单据，不能反审核");
        }
        // 1.反审核
        produceConsuming.setAuditId(null);
        produceConsuming.setAuditTime(null);
        produceConsuming.setAuditStatus(Const.AUDIT_STATUS_WAIT);
        this.updateById(produceConsuming);
        // 2.反写投料计划已领数量
        this.updateFeedingPlanDetail(id, Const.AUDIT_STATUS_WAIT);
        // 3.更新库存
        List<InOrOutStock> inOrOutStockList = this.getInOrOutStockList(id);
        stockService.updateInventory(Const.SourceBillType.SCLY, produceConsuming.getConsumingCode(), inOrOutStockList, Const.ChangeType.ADD);
    }

    @Override
    public void saveAudit(ScmProduceConsumingDTO dto) {
        // 保存
        Integer id = this.tempSave(dto);
        // 审核
        this.audit(id);
    }

}




